<?php
/**
 * Collection de fonctions relative aux calculs des fonctions de Eve
 * @author Joseph Lemoine - lemoine.joseph@gmail.com
 * @version: 3
 */
class EveMath {

    /**
     * Liste des prix déjà récupéré
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @var	array	[id][all]
     *			[id][buy]
     *			[id][sell]
     */
    private static $_priceLoaded;

    /**
     * Liste des BPC inventé ayant 1 seul run, le group correspond au
     * groupe de l'item dans la bdd d'eve.
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @var	array	(group => true)
     */
    private static $_oneRunBPC;

    /**
     * Liste des groupes où ne pas appliquer le wastage factor
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @var	array	(group => true)
     */
    private static $_avoidGroup;

    /**
     * Liste des menu du market à ne pas charger
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @var	array	(group => true)
     */
    private static $_avoidMarket;

    /**
     * Liste des id où ne pas appliquer le wastage factor
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @var	array	[id] = true
     */
    private static $_avoidID;


    /**
     * Calcul des pertes du à la recherche ME du BPO
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @param	int	$materialEfficiency Niveau de recherche du BPO
     * @param	int	$materialAmount	    Quantité de matériaux requis
     * @param	int	$baseWasteFactor    Facteur de gaspillage du BPO
     * @return	int			    quantité perdue
     * @access	public
     */
    public static function waste($materialEfficiency, $materialAmount, $baseWasteFactor) {
	if($materialEfficiency >= 0 )
	    return round($materialAmount * ($baseWasteFactor / 100) * (1 / ($materialEfficiency + 1)));
	else
	    return round($materialAmount * ($baseWasteFactor / 100) * (1 - $materialEfficiency));
    }

    /**
     * Calcul du temps de production d'un item
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @param	evebpo	$bpo			    BPO servant à produire cet item
     * @param	float	$installationMultiplicator  Multiplicateur de l'installation
     * @return	int				    Temps en seconde
     * @access	Public
     */
    public static function productionTime($bpo, $installationMultiplicator = 1) {
	/* Si notre BPO à bien un temps de production */
	if(!empty($bpo->productionTime)){
	    /* Calcul du modificateur (pfiou) */
	    $productionTimeModifier = (1 - (0.04*INDUSTRYSKILL)) 
		* (1 - IMPLANTMODIFIER)
		* $installationMultiplicator;

	    /* Application du PE selon si il est positif ou négatif */
	    if($bpo->carac->pe >= 0)
		$PEModifier = $bpo->carac->pe / (1+$bpo->carac->pe);
	    else
		$PEModifier = $bpo->carac->pe - 1;

	    /* Calcul du temps de prod pondéré par le $productionTimeModifier */
	    $productionTime = $bpo->productionTime
		* (1
		    - ($bpo->productivityModifier / $bpo->productionTime)
		    * $PEModifier )
		* $productionTimeModifier;

	    /* Renvoit du temps de prod (enfin) */
	    return round($productionTime);
	}
    }

    /**
     * Calcul des chances de réussite d'une invention
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @param	Object	$item	Données de l'item T2 à inventer
     * @return	float		pourcentage de chance de réussite
     * @access	public
     */
    public static function inventionChance($baseChance = 0.4, $meta = 0, $decryptorMod = 1) {
	/**
	 * Base chance is 20% for battlecruisers, battleships, Hulk
	 ** Base chance is 25% for cruisers, industrials, Mackinaw
	 *** Base chance is 30% for frigates, destroyers, Skiff, freighters
	 **** Base chance is 40% for all other inventables
	 */
	
	$chance = $baseChance
	    * (1+(0.01*ENCRSKILL))
	    * (1+((SKILLA + SKILLB)
	    * (0.1 / (5 - $meta))))
	    * $decryptorMod;

	/* On ne peut pas avoir plus de 100% de réussite */
	$chance = $chance > 1 ? 1 : $chance;

	return $chance;

	// NOTE POUR LE T3
	// [16:40:52] Darkhus > Reverse_Chance =
	// Base_Chance
	// * (1 + (0.01 * Reverse_Engineering_Skill_Level))
	// * (1 + (0.1 * (Datacore_1_Skill_Level + Datacore_2_Skill_Level)))
	// Base is:
	// Wrecked = 20%
	// Malfunctioning = 30%
	// Intact = 40%
    }

    /**
     * Liste des groups et ID sur lesquels on ne doit pas appliquer le gaspillage
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @param	int	$groupID    ID du groupe testé
     * @param	int	$typeID	    ID de l'item à tester
     * @return	bool
     * @access	public
     */
    public static function haveToApplyWaste($groupID, $typeID = NULL) {
	/* test de l'ID de l'objet si on à spécifier un ID */
	if(self::$_avoidID[$typeID])
	    return FALSE;

	/* Chargement des groupes à éviter */
	if(empty(self::$_avoidGroup))
	    self::loadAvoidGroup();

	/* Test du groupe, si il n'est pas dans la liste, on renvoi "autorisé" */
	if(!self::$_avoidGroup[$groupID])
	    return TRUE;
	/* Sinon on renvoit non autorisé */
	else
	    return FALSE;
    }

    /**
     * chargement des groupes où ne pas appliquer le gaspillage
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @param	VOID
     * @return	bool
     * @access	private
     */
    private static function loadAvoidGroup() {
	self::$_avoidGroup[332] = true; // tools

	/* Récupération des groupes associés aux vaisseaux et aux modules */
	$sql = "SELECT g.groupID FROM invgroups AS g WHERE g.categoryID = 6 OR g.categoryID = 7";
	$req = Sql::run($sql, "EVE");

	/* Ajout des groupes à la variable static */
	foreach($req AS $row => $data)
	    self::$_avoidGroup[$data["groupID"]] = true;

	return true;
    }

    /**
     * Chargement des groupes où ne pas appliquer les BCP 10 runs
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @param	VOID
     * @return	bool
     * @access	private
     */
    private static function loadOneRunBPC() {
	/* Les bombs n'ont qu'un seul run */
	self::$_oneRunBPC[90] = true; // Bombs

	/* Récupération des groupes associés aux vaisseaux */
	$sql = "SELECT g.groupID FROM invgroups AS g WHERE g.categoryID = 6";
	$req = Sql::run($sql, "EVE");

	/* Ajout des groupes à la variable static */
	foreach($req AS $row => $data)
	    self::$_oneRunBPC[$data["groupID"]] = true;

	return true;
    }

    /**
     * Méthode d'ajout manuel d'id où ne pas appliquer le gaspillage
     * Utilisé pour les versions T1 des items T2
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @param	int	$typeID	    Id de l'item
     * @return	true		    Toujours vraie, mais on s'en fout
     * @access	public
     */
    public static function addAvoidID($typeID) {
	self::$_avoidID[$typeID] = true;
	
	return true;
    }

    /**
     * Renvoit true si c'est un BPO à inventer
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @param	instanceof(evebpo)
     * @return	bool
     * @access	public
     */
    public static function canBeInvented($bpo){
	if($bpo instanceof evebpo){
	    if($bpo->techLevel == 2
		&& $bpo->parent->groupID != 913 // Capital compo T2
		&& $bpo->parent->groupID != 334 // Compo T2
		&& $bpo->parent->groupID != 332)
		return TRUE;
	    else
		return FALSE;
	}else
	    return FALSE;
    }

    /**
     * Renvoit le nombre de run max du BPC T2
     * Si c'est un ship : 1, sinon 10
     * Si c'est un rig : 1 quand même
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @param	int	$groupID    Identifiant du groupe de l'item
     * @return
     */
    public static function getRuns($groupID) {
	if(empty(self::$_oneRunBPC))
	    self::loadOneRunBPC();

	/* Si c'est un ship, on renvoit 1, sinon 10 */
	if(self::$_oneRunBPC[$groupID])
	    return 1;
	else
	    return 10;
    }

    /**
     * Définie les matériaux de base d'un item
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @param	int	$marketId   Id du tableau market de l'item
     * @return	bool
     * @access	public
     */
    public static function isBaseMaterial($marketId) {
	switch($marketId){
	    case 18:
		return TRUE;
		break;
	    case 499:
		return TRUE;
		break;
	    case 980:
		return TRUE;
		break;
	    case 1146:
		return TRUE;
		break;
	    case 1148:
		return TRUE;
		break;
	    case 1335:
		return TRUE;
		break;
	    default:
		return FALSE;
	}
    }

    /**
     * Test si un groupe doit êrte inclu dans la cascade de prix ou non
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @param	int	$groupID    ID du groupe à ne pas inclure
     * @return	bool
     * @access	public
     */
    public static function includeCost($groupID) {
	switch($groupID){
	    case 716:
		return TRUE;
		break;
	    default:
		return FALSE;
	}
    }

    /**
     * Récupération des moyennes de prix en buy order sur eve central
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @param	array	(row => itemID)
     * @return	array	(itemID => float prix)
     * @access	public
     */
    private static function getBOPrice($listID){

	/* Ajout des différents id */
	foreach($listID AS $id){
	    /* On limite le nombre d'item simultané à 5 !*/
	    if($limit >= 5){
		$req++;
		$limit = 0;
	    }

	    /* Ecriture de l'URL à appeler */
	    if(empty($url[$req]))
		$url[$req] = API_HOST_PRICES;

	    $url[$req] .= "typeid=" . $id . "&";

	    $limit++;
	}

	/* Limite de région, ici The Forge */
	// à passer en argument !
	foreach($url AS $req => $api){
	    $api .= "regionlimit=10000002";

	    /* Tentative de cahrgement du XML de eve-central */
	    try{
		/* Pour éviter les plantage du à eve central */
		set_time_limit(15);
		/* Chargement */
		$priceList = simplexml_load_file($api);
	    }catch(Exception $e){
		$priceList = 0;
	    }
	    
	    /* Réinitialisation du timer */
	    set_time_limit(60);

	    if(!empty($priceList)){
		if(count($priceList->marketstat->type) > 1){
		    /* Pour tout les items checkés */
		    foreach($priceList->marketstat->type AS $row => $prop){
			/* Récupération de l'ID */
			$att = (INT) ($prop->attributes()->id);
			/* Ecriture du tableau de prix à renvoyer */
			$send[$att]["all"] = (FLOAT) ($prop->all->avg);
			$send[$att]["buy"] = (FLOAT) ($prop->buy->max);
			$send[$att]["sell"] = (FLOAT) ($prop->sell->min);
		    }
		}else{
		    /* Récupération de l'ID */
		    $att = (INT) ($priceList->marketstat->type->attributes()->id);
		    /* Ecriture du tableau de prix à renvoyer */
		    $send[$att]["all"] = (FLOAT) ($priceList->marketstat->type->all->avg);
		    $send[$att]["buy"] = (FLOAT) ($priceList->marketstat->type->buy->max);
		    $send[$att]["sell"] = (FLOAT) ($priceList->marketstat->type->sell->min);
		}
	    }else{
		return FALSE;
	    }
	}

	return $send;
    }

    /**
     * Renvois une liste de prix pour chaque item
     * Si le prix n'est pas à jour, fait un check sur l'aggregator spécifié dans
     * le fichier include/init.php
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @param	array	$listID (id1, id2 ...)
     * @return	array		[itemID] = float    prix;
     * @access	public
     */
    public static function getPrice($listID){
	/* Ecriture de la requète de sélection des prix dans la bdd */
	if(!empty($listID)){

	    /* Déclaration de $checkPrices */
	    $checkPrices = array();

	    /* Scan de tout les ID */
	    foreach($listID AS $id){
		/* Si on à déjà demandé ce prix */
		if(!empty(self::$_priceLoaded[$id])){
		    /* Si le prix en sock est valide */
		    if(self::$_priceLoaded[$id] instanceof icuMarPrices)
			/* On renvoit l'objet déjà chargé */
			$send[$id] = self::$_priceLoaded[$id];
		    /* Si on à déjà tenter de récupérer ce prix et que ce fût un échec */
		    else
			$send[$id] = 0;
		}else{
		    /* Sinon on cherche d'abord dans la bdd... */
		    $marketPrice[$id] = new icuMarPrices($id);
		    
		    /* Si l'entrée existe on prépare le renvoit du prix du meilleur BO */
		    if($marketPrice[$id]->load($id)){
			
			/* Si la dernière update n'est pas à jours */
			if(($marketPrice[$id]->lastUpdate + PRICE_UPDATE_DELAY < time()
				/* Ou vide */
				|| empty($marketPrice[$id]->buy)
				|| empty($marketPrice[$id]->all)
				|| empty($marketPrice[$id]->sell)
				/* Et qu'on ne l'a pas paramétré en update manuelle  */
			    ) && !$marketPrice[$id]->manualUpdate
			    && ALLOW_AUTO_PRICECHECK){

			    /* Ajout de l'entrée à scanner */
			    array_push($checkPrices, $id);
			    
			/* Sinon si le prix est valide dans le temps et la forme */
			}else{
			    /* Ajout à la liste des prix déjà récupéré */
			    self::$_priceLoaded[$id] = $marketPrice[$id];

			    /* on renvoit le prix qu'on à déjà en bdd */
			    $send[$id] = $marketPrice[$id];
			}
		    }else{
			/* Ajout de l'entrée à scanner */
			array_push($checkPrices, $id);
		    }
		}
	    }

	    /* Mise à jour des prix d'item obselète ou non renseignés */
	    if(!empty($checkPrices)){
		/* Récupération sur l'agrégateur de market préféré */
		$newPrices = self::getBOPrice($checkPrices);

		if(!empty($newPrices)){
		    foreach($newPrices AS $id => $price){
			/* Mise à jour de la bdd */
			$marketPrice[$id]->itemID = $id;
			$marketPrice[$id]->all = $price["all"];
			$marketPrice[$id]->buy = $price["buy"];
			$marketPrice[$id]->sell = $price["sell"];
			$marketPrice[$id]->lastUpdate = time();
			$marketPrice[$id]->manualUpdate = 0;
			/**
			 * NOTE:
			 * L'enregistrement se fera lors du __destruct()
			 */

			/* Ajout de l'objet à la liste des prix déjà récupéré */
			self::$_priceLoaded[$id] = $marketPrice[$id];

			/* Ajout dans la liste des renvois */
			$send[$id] = $marketPrice[$id];
		    }
		}else{
		    /* Ajout dans la liste des renvois */
		    $send[$id] = -1;

		    /* Ajout à la liste des prix déjà récupéré */
		    self::$_priceLoaded[$id] = -1;
		}
	    }

	    /* Renvois des prix */
	    return $send;
	}else{
	    return FALSE;
	}
    }

    /**
     * Calcule le meilleur ME possible pour un BPO
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @param	evebpo	$bpo	objet evebpo à scanner
     * @return	int		ME perfect
     * @access	public
     */
    public static function findBestME($bpo) {
	/* Chargement des items requis pour la production */
	$list = $bpo->requiredComp(1);
	
	/* Test pour tout les compos requis */
	foreach($list AS $typeID => $qtt){
	    /* Meilleur ME pour le compo regardé */
	    $tmpBestME = floor(2 * $qtt * ($bpo->wasteFactor / (1+$bpo->wasteFactor)) - 1);
	    
	    /* Si il est supérieur à l'ancien, on remplace */
	    $bestME = $bestME < $tmpBestME ? $tmpBestME : $bestME;
	}

	/* On renvoit le plus gros ME */
	return (INT) $bestME;
    }

    /**
     * Test si le groupe de marché sélectionné est exclu de la liste des ventes ou non
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @param	int	$marketGroupID	    Id du menu à tester
     * @return	bool			    TRUE si on doit l'exclure
     *					    FALSE si on doit le garder
     * @access	public
     */
    public static function excludeMarketGroup($marketGroupID) {
	/* Chargement de la liste des groupes à exclure */
	if(!self::$_avoidMarket)
	    self::loadAvoidMarket();

	/* Test du groupe */
	if(self::$_avoidMarket[$marketGroupID])
	    /* Si on doit l'exclure, on renvoit TRUE */
	    return TRUE;
	else
	    /* Sinon on le garde en renvoyant FALSE */
	    return FALSE;
    }

    /**
     * Charge la liste des menu du market à ne pas garder
     * et les stock dans une variable static
     * @author	Joseph Lemoine - lemoine.joseph@gmail.com
     * @param	VOID
     * @return	VOID
     * @access	private
     */
    private static function loadAvoidMarket() {
		$sql = "SELECT
			i.marketGroupID
			FROM itmavoidmarket AS i";
		$req = Sql::run($sql, "ICU");

		foreach($req AS $row => $data){
			self::$_avoidMarket[$data["marketGroupID"]] = TRUE;
		}
    }
}
?>
